package server

import com.sun.net.httpserver.HttpExchange
import com.sun.net.httpserver.HttpHandler
import com.sun.net.httpserver.HttpServer
import data.InterruptItem
import data.ScheduleItem
import data.ScheduleResponseData
import utils.encodeResponseData
import java.lang.StringBuilder
import java.net.InetSocketAddress
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
import java.util.regex.Pattern
import kotlin.collections.HashMap
import kotlin.collections.HashSet

/**
 * @FileName: PowerSocketServer.kt
 * @Description:
 * @Author: Gene
 * @Email: indianpapa@gmail.com
 * @Date: 2024/11/15 16:58
 */

class PowerSocketServer(port: Int = 3389) {
    private val schedulesPattern = Pattern.compile("(?<=\\[)[^\\[\\]]*(?=\\])")
    private val workThreadPool = Executors.newFixedThreadPool(8)
    private var scheduleVersion = 0
    private var interruptVersion = 0
    private var httpServer = HttpServer.create(InetSocketAddress(port), 0)

    private val dataLock = ReentrantLock()
    private val scheduleItems = LinkedList<ScheduleItem>()
    private val interruptItems = LinkedList<InterruptItem>()
    private val status = HashMap<Int, Boolean>()

    private var lastStatusUpdateTime = 0L

    private val updateStatusHandler = object: MyHandler(1) {
        override fun doHandle(exchange: HttpExchange, params: Map<String, String?>) {
            // http://localhost:8001/pws/upste?v=1&d=1:1;2:0;3:0
            println("updateStatusHandler")

            try {
                dataLock.lock()
                lastStatusUpdateTime = System.currentTimeMillis()
                params["d"]?.split(";")?.forEach { state ->
                    println("update state: $state")
                    val pair = state.split(":")
                    if (pair.size == 2 && pair[0].isNumber() && pair[1].isNumber()) {
                        status[pair[0].toInt()] = pair[1] == "1"
                    }
                }

                val payload = encodeResponseData(ScheduleResponseData(scheduleItems, scheduleVersion % MAX_VERSION_N, interruptItems, interruptVersion % MAX_VERSION_N))
                val bytes = payload.encodeToByteArray()
                exchange.sendResponseHeaders(200, bytes.size.toLong())
                exchange.responseBody?.write(bytes)
                exchange.close()
            } finally {
                dataLock.unlock()
            }
        }
    }

    private val updateScheduleHandler = object: MyHandler(1) {
        override fun doHandle(exchange: HttpExchange, params: Map<String, String?>) {
            println("updateScheduleHandler")

            params["d"]?.let { data ->
                when(params["act"]) {
                    "upscd" -> updateSchedule(params, data)
                    "upint" -> updateInterrupt(params, data)
                    else -> {}
                }
            }

            try {
                dataLock.lock()
                val payload = status.map { "${it.key}:${it.value.toInt()}" }.joinToString(separator = ";")

                val bytes = if (status.isEmpty()) "No status".encodeToByteArray() else payload.encodeToByteArray()
                exchange.sendResponseHeaders(200, bytes.size.toLong())
                exchange.responseBody?.write(bytes)
                exchange.close()
            } finally {
                dataLock.unlock()
            }
        }
    }

    private val getScheduleHandler = object : MyHandler(1) {
        override fun doHandle(exchange: HttpExchange, params: Map<String, String?>) {
            try {
                dataLock.lock()
                val sb = StringBuilder()
                // scd:{1[3,5,6][3,5,6];2[3,6,8][4,7,9]};inr:[1:1;2:0];ste:[]:upt:1893281983291
                if (scheduleItems.isNotEmpty()) {
                    sb.append("scd:{")
                    scheduleItems.forEach {item ->
                        sb.append(item.id.toString())
                        sb.append("[")
                        sb.append(item.turnOnTimeList.joinToString(","))
                        sb.append("]")
                        sb.append("[")
                        sb.append(item.turnOffTimeList.joinToString(","))
                        sb.append("]")
                    }
                    sb.append("}")
                }
                if (interruptItems.isNotEmpty()) {
                    sb.append(";inr:[")
                    sb.append(interruptItems.joinToString(separator = ",") {item ->
                        "${item.id}:${item.action.toInt()}"
                    })
                    sb.append("]")
                }
                sb.append(":upt:${System.currentTimeMillis() - lastStatusUpdateTime}")
                val payload = sb.toString()
                val bytes = payload.encodeToByteArray()
                exchange.sendResponseHeaders(200, bytes.size.toLong())
                exchange.responseBody?.write(bytes)
                exchange.close()
            } finally {
                dataLock.unlock()
            }
        }
    }

    init {
        httpServer.createContext("/pws/upste", updateStatusHandler)
        httpServer.createContext("/pws/upscd", updateScheduleHandler)
        httpServer.createContext("/pws/getscd", getScheduleHandler)
    }

    fun start() {
        println("Start server")
        httpServer.start()
    }

    fun stop() {
        workThreadPool.shutdown()
        workThreadPool.awaitTermination(5, TimeUnit.SECONDS)
    }

    private fun updateSchedule(params: Map<String, String?>, data: String) {
        // http://localhost:8001/pws/upscd?v=1&act=upscd&d=1[1,2,3,4][];6[3,4,5,6][7,8,9,0];12[][7,8,0]
        val set = HashSet<ScheduleItem>()
        data.split(";").forEach { item ->
            createScheduleItem(item)?.let { scdItem ->
                println("${scdItem.id}; ${scdItem.turnOnTimeList}; ${scdItem.turnOffTimeList}")
                set.add(scdItem)
            }
        }
        try {
            dataLock.lock()
            scheduleVersion++
            scheduleItems.clear()
            scheduleItems.addAll(set)
        } finally {
            dataLock.unlock()
        }
    }

    private fun updateInterrupt(params: Map<String, String?>, data: String) {
        // http://localhost:8001/pws/upscd?v=1&act=upint&d=1:1;2:0;3:0
        val set = HashSet<InterruptItem>()
        data.split(";").forEach { item ->
            val pair = item.split(":")
            if (pair.size == 2 && pair[0].isNumber() && pair[1].isNumber()) {
                val interruptItem = InterruptItem(pair[0].toInt(), pair[1] == "1")
                set.add(interruptItem)
                println("intr: ${interruptItem.id}; ${interruptItem.action}")
            }
        }
        try {
            dataLock.lock()
            interruptVersion++
            interruptItems.clear()
            interruptItems.addAll(set)
        } finally {
            dataLock.unlock()
        }
    }

    private fun createScheduleItem(content: String): ScheduleItem? {
        val matcher = schedulesPattern.matcher(content)
        var isOn = true
        val onList = LinkedList<Int>()
        val offList = LinkedList<Int>()

        val index = content.substringBefore("[", "invalid")
        if (!index.isNumber()) {
            return null
        }

        while (matcher.find()) {
            val list = if (isOn) onList else offList
            isOn = false
            matcher.group().notEmptyOrNull()?.split(",")?.forEach {
                list.addLast(it.toInt())
            }
        }
        return ScheduleItem(index.toInt(), onList, offList)
    }

    class Device(val id: Int) {
        var currentState: Int = 0 // 0: Unknown, 1: On 2: Off
        var interruptCfg: Int = 0 // 0: No interrupt 1: on 2: off
        val scheduleOnList = LinkedList<Int>()
        val scheduleOffList = LinkedList<Int>()
    }

    private fun String.notEmptyOrNull(): String? {
        return if (this.isNotEmpty() && this.isNotBlank()) this else null
    }

    private fun String.isNumber() : Boolean {
        return this.none { !it.isDigit() }
    }

    private fun Boolean.toInt(): Int {
        return if (this) 1 else 0
    }

    private abstract inner class MyHandler(val version: Int) : HttpHandler {
        override fun handle(exchange: HttpExchange?) {
            exchange?.let {
                workThreadPool.execute {
                    val uri = exchange.requestURI
                    val query = uri.query
                    val params = query?.split("&")?.associate {
                        val pair = it.split("=")
                        pair[0] to pair.getOrNull(1)
                    } ?: emptyMap()
                    if (params["v"].equals("1")) {
                        doHandle(it, params)
                    } else {
                        exchange.sendResponseHeaders(200, 0)
                        exchange.responseBody?.write("Bad Api Version".toByteArray())
                        exchange.close()
                    }
                }
            }
        }

        abstract fun doHandle(exchange: HttpExchange, params: Map<String, String?>)
    }

    private companion object {
        private const val MAX_VERSION_N = 79
    }
}